/*******************************************************************************
 * Copyright (c) 2010 European Software Institute - Tecnalia.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Author - Adri�n Noguero (adrian.noguero@esi.es)
 *     
 *******************************************************************************/

package es.esi.gemde.modelvalidator.service;

import java.util.List;
import java.util.Vector;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.ocl.OCLInput;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.Constraint;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.utilities.UMLReflection;

import es.esi.gemde.core.resources.IGemdeResource.ResourceType;
import es.esi.gemde.modelvalidator.exceptions.ConstraintCreationException;
import es.esi.gemde.modelvalidator.resources.OCLConstraint;
import es.esi.gemde.modelvalidator.resources.OCLConstraintDescriptor;
import es.esi.gemde.modelvalidator.service.impl.ModelValidatorConstraint;


/**
 * An implementation of IConstraintFactory for OCL based constraints.
 * This factory will create a constraint instance from an OCL string that defines a single OCL Invariant.
 *
 * @author Adrian Noguero (adrian.noguero@tecnalia.com)
 * @version 1.0
 * @since 1.0
 *
 */
public class OCLConstraintFactory implements IConstraintFactory {

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.AbstractConstraintFactory#createConstraint()
	 */
	@Override
	public IModelValidatorConstraint createConstraint(String name, String category, int severity, String body, boolean isProtected) throws IllegalArgumentException, ConstraintCreationException {
		return createConstraint(name, "", category, severity, body, isProtected);
	}

	/* (non-Javadoc)
	 * @see es.esi.gemde.modelvalidator.service.IConstraintFactory#createConstraint(java.lang.String, java.lang.String, java.lang.String, int, java.lang.String, boolean)
	 */
	@Override
	public IModelValidatorConstraint createConstraint(String name,
			String description, String category, int severity, String body,
			boolean isProtected) throws IllegalArgumentException,
			ConstraintCreationException {
		// Check inputs
		if (body == null) {
			throw new IllegalArgumentException("No constraint body was provided");
		}
		
		if (severity != IStatus.ERROR && severity != IStatus.INFO && severity != IStatus.WARNING) {
			throw new IllegalArgumentException("Illegal severity value was provided");
		}
		
		// Parse the OCL string in "body"
		OCLInput input = new OCLInput(body);
		OCL ocl = OCL.newInstance();
		
		try {
			ocl.parse(input);
		}
		catch (ParserException pe) {
			// A parser exception may occur because the metamodel of the constraint is not loaded.
			// If that should happen we will load all the metamodels in the EPackage.Registry and try again.
			if (pe.getMessage().indexOf("Unable to find package:") != -1) {
				String packageName = pe.getMessage().substring(pe.getMessage().indexOf('(')+1, pe.getMessage().indexOf(')')).trim();
				
				boolean found = false;
				String [] registryKeys = EPackage.Registry.INSTANCE.keySet().toArray(new String [0]);
				for (int i = 0; i < registryKeys.length; i++) {
					try {
						if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage) {
							if (((EPackage)EPackage.Registry.INSTANCE.get(registryKeys[i])).getName().equals(packageName))
								found = true;
						}
						else if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage.Descriptor) {
							if (((EPackage.Descriptor)EPackage.Registry.INSTANCE.get(registryKeys[i])).getEPackage().getName().equals(packageName))
								found = true;
						}
					}
					catch (Exception e) {
						// Just ignore the package with the error and go on looking for other packages.
					}
				}
				
				if (!found) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}
				
				try {
					ocl.parse(input);
				}
				catch (ParserException pe2) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}
			}
			else {
				throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
			}
		}
		
		// Get the parsed constraints
		List<Constraint> parsedConstraints = new Vector<Constraint>();
		
		// Keep only invariants
		for (Constraint constraint : ocl.getConstraints()) {
			if (UMLReflection.INVARIANT.equals(constraint.getStereotype())) {
				parsedConstraints.add(constraint);
			}
		}
		
		// If no constraints where parsed we'll throw an exception
		if (parsedConstraints == null || parsedConstraints.size() == 0) {
			throw new ConstraintCreationException("No constraints where parsed");
		}
		
		// If more than one constraint is in the string we also throw an exception
		if (parsedConstraints.size() > 1) {
			throw new ConstraintCreationException("The OCL string contains more than one constraint");
		}
		
		// Create the constraint using the structure classes needed
		OCLConstraintDescriptor desc = new OCLConstraintDescriptor(category, parsedConstraints.get(0), severity, body);
		OCLConstraint implementation = new OCLConstraint(desc, ocl);
		ModelValidatorConstraint constructedConstraint = null;
		
		// If no name is provided, we use the name included in the ocl string
		if (name != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, name, category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), isProtected ? ResourceType.PROTECTED : ResourceType.NORMAL);
		}
		else if (desc.getName() != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, desc.getName(), category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), isProtected ? ResourceType.PROTECTED : ResourceType.NORMAL);
		}
		else {
			throw new ConstraintCreationException("No name was provided for the constraint");
		}
		
		return constructedConstraint;
	}

	@Override
	public IModelValidatorConstraint createConstraint(String name,
			String category, int severity, String body)
			throws IllegalArgumentException, ConstraintCreationException {
		// Check inputs
		if (body == null) {
			throw new IllegalArgumentException("No constraint body was provided");
		}

		if (severity != IStatus.ERROR && severity != IStatus.INFO && severity != IStatus.WARNING) {
			throw new IllegalArgumentException("Illegal severity value was provided");
		}

		// Parse the OCL string in "body"
		OCLInput input = new OCLInput(body);
		OCL ocl = OCL.newInstance();

		try {
			ocl.parse(input);
		}
		catch (ParserException pe) {
			// A parser exception may occur because the metamodel of the constraint is not loaded.
			// If that should happen we will load all the metamodels in the EPackage.Registry and try again.
			if (pe.getMessage().indexOf("Unable to find package:") != -1) {
				String packageName = pe.getMessage().substring(pe.getMessage().indexOf('(')+1, pe.getMessage().indexOf(')')).trim();

				boolean found = false;
				String [] registryKeys = EPackage.Registry.INSTANCE.keySet().toArray(new String [0]);
				for (int i = 0; i < registryKeys.length; i++) {
					try {
						if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage) {
							if (((EPackage)EPackage.Registry.INSTANCE.get(registryKeys[i])).getName().equals(packageName))
								found = true;
						}
						else if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage.Descriptor) {
							if (((EPackage.Descriptor)EPackage.Registry.INSTANCE.get(registryKeys[i])).getEPackage().getName().equals(packageName))
								found = true;
						}
					}
					catch (Exception e) {
						// Just ignore the package with the error and go on looking for other packages.
					}
				}

				if (!found) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}

				try {
					ocl.parse(input);
				}
				catch (ParserException pe2) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}
			}
			else {
				throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
			}
		}

		// Get the parsed constraints
		List<Constraint> parsedConstraints = new Vector<Constraint>();

		// Keep only invariants
		for (Constraint constraint : ocl.getConstraints()) {
			if (UMLReflection.INVARIANT.equals(constraint.getStereotype())) {
				parsedConstraints.add(constraint);
			}
		}

		// If no constraints where parsed we'll throw an exception
		if (parsedConstraints == null || parsedConstraints.size() == 0) {
			throw new ConstraintCreationException("No constraints where parsed");
		}

		// If more than one constraint is in the string we also throw an exception
		if (parsedConstraints.size() > 1) {
			throw new ConstraintCreationException("The OCL string contains more than one constraint");
		}

		// Create the constraint using the structure classes needed
		OCLConstraintDescriptor desc = new OCLConstraintDescriptor(category, parsedConstraints.get(0), severity, body);
		OCLConstraint implementation = new OCLConstraint(desc, ocl);
		ModelValidatorConstraint constructedConstraint = null;

		// If no name is provided, we use the name included in the ocl string
		if (name != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, name, category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.NORMAL);
		}
		else if (desc.getName() != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, desc.getName(), category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.NORMAL);
		}
		else {
			throw new ConstraintCreationException("No name was provided for the constraint");
		}

		return constructedConstraint;
	}

	@Override
	public IModelValidatorConstraint createProtectedConstraint(String name,
			String category, int severity, String body)
					throws IllegalArgumentException, ConstraintCreationException {
		// Check inputs
		if (body == null) {
			throw new IllegalArgumentException("No constraint body was provided");
		}

		if (severity != IStatus.ERROR && severity != IStatus.INFO && severity != IStatus.WARNING) {
			throw new IllegalArgumentException("Illegal severity value was provided");
		}

		// Parse the OCL string in "body"
		OCLInput input = new OCLInput(body);
		OCL ocl = OCL.newInstance();

		try {
			ocl.parse(input);
		}
		catch (ParserException pe) {
			// A parser exception may occur because the metamodel of the constraint is not loaded.
			// If that should happen we will load all the metamodels in the EPackage.Registry and try again.
			if (pe.getMessage().indexOf("Unable to find package:") != -1) {
				String packageName = pe.getMessage().substring(pe.getMessage().indexOf('(')+1, pe.getMessage().indexOf(')')).trim();

				boolean found = false;
				String [] registryKeys = EPackage.Registry.INSTANCE.keySet().toArray(new String [0]);
				for (int i = 0; i < registryKeys.length; i++) {
					try {
						if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage) {
							if (((EPackage)EPackage.Registry.INSTANCE.get(registryKeys[i])).getName().equals(packageName))
								found = true;
						}
						else if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage.Descriptor) {
							if (((EPackage.Descriptor)EPackage.Registry.INSTANCE.get(registryKeys[i])).getEPackage().getName().equals(packageName))
								found = true;
						}
					}
					catch (Exception e) {
						// Just ignore the package with the error and go on looking for other packages.
					}
				}

				if (!found) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}

				try {
					ocl.parse(input);
				}
				catch (ParserException pe2) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}
			}
			else {
				throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
			}
		}

		// Get the parsed constraints
		List<Constraint> parsedConstraints = new Vector<Constraint>();

		// Keep only invariants
		for (Constraint constraint : ocl.getConstraints()) {
			if (UMLReflection.INVARIANT.equals(constraint.getStereotype())) {
				parsedConstraints.add(constraint);
			}
		}

		// If no constraints where parsed we'll throw an exception
		if (parsedConstraints == null || parsedConstraints.size() == 0) {
			throw new ConstraintCreationException("No constraints where parsed");
		}

		// If more than one constraint is in the string we also throw an exception
		if (parsedConstraints.size() > 1) {
			throw new ConstraintCreationException("The OCL string contains more than one constraint");
		}

		// Create the constraint using the structure classes needed
		OCLConstraintDescriptor desc = new OCLConstraintDescriptor(category, parsedConstraints.get(0), severity, body);
		OCLConstraint implementation = new OCLConstraint(desc, ocl);
		ModelValidatorConstraint constructedConstraint = null;

		// If no name is provided, we use the name included in the ocl string
		if (name != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, name, category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.PROTECTED);
		}
		else if (desc.getName() != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, desc.getName(), category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.PROTECTED);
		}
		else {
			throw new ConstraintCreationException("No name was provided for the constraint");
		}

		return constructedConstraint;
	}

	@Override
	public IModelValidatorConstraint createNetworkedConstraint(String name,
			String category, int severity, String body)
					throws IllegalArgumentException, ConstraintCreationException {
		// Check inputs
		if (body == null) {
			throw new IllegalArgumentException("No constraint body was provided");
		}

		if (severity != IStatus.ERROR && severity != IStatus.INFO && severity != IStatus.WARNING) {
			throw new IllegalArgumentException("Illegal severity value was provided");
		}

		// Parse the OCL string in "body"
		OCLInput input = new OCLInput(body);
		OCL ocl = OCL.newInstance();

		try {
			ocl.parse(input);
		}
		catch (ParserException pe) {
			// A parser exception may occur because the metamodel of the constraint is not loaded.
			// If that should happen we will load all the metamodels in the EPackage.Registry and try again.
			if (pe.getMessage().indexOf("Unable to find package:") != -1) {
				String packageName = pe.getMessage().substring(pe.getMessage().indexOf('(')+1, pe.getMessage().indexOf(')')).trim();

				boolean found = false;
				String [] registryKeys = EPackage.Registry.INSTANCE.keySet().toArray(new String [0]);
				for (int i = 0; i < registryKeys.length; i++) {
					try {
						if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage) {
							if (((EPackage)EPackage.Registry.INSTANCE.get(registryKeys[i])).getName().equals(packageName))
								found = true;
						}
						else if (EPackage.Registry.INSTANCE.get(registryKeys[i]) instanceof EPackage.Descriptor) {
							if (((EPackage.Descriptor)EPackage.Registry.INSTANCE.get(registryKeys[i])).getEPackage().getName().equals(packageName))
								found = true;
						}
					}
					catch (Exception e) {
						// Just ignore the package with the error and go on looking for other packages.
					}
				}

				if (!found) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}

				try {
					ocl.parse(input);
				}
				catch (ParserException pe2) {
					throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
				}
			}
			else {
				throw new ConstraintCreationException("OCL parser threw an Exception: " + pe.getMessage());
			}
		}

		// Get the parsed constraints
		List<Constraint> parsedConstraints = new Vector<Constraint>();

		// Keep only invariants
		for (Constraint constraint : ocl.getConstraints()) {
			if (UMLReflection.INVARIANT.equals(constraint.getStereotype())) {
				parsedConstraints.add(constraint);
			}
		}

		// If no constraints where parsed we'll throw an exception
		if (parsedConstraints == null || parsedConstraints.size() == 0) {
			throw new ConstraintCreationException("No constraints where parsed");
		}

		// If more than one constraint is in the string we also throw an exception
		if (parsedConstraints.size() > 1) {
			throw new ConstraintCreationException("The OCL string contains more than one constraint");
		}

		// Create the constraint using the structure classes needed
		OCLConstraintDescriptor desc = new OCLConstraintDescriptor(category, parsedConstraints.get(0), severity, body);
		OCLConstraint implementation = new OCLConstraint(desc, ocl);
		ModelValidatorConstraint constructedConstraint = null;

		// If no name is provided, we use the name included in the ocl string
		if (name != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, name, category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.NETWORKED);
		}
		else if (desc.getName() != null) {
			constructedConstraint = new ModelValidatorConstraint(implementation, desc.getName(), category, (EPackage) parsedConstraints.get(0).getConstrainedElements().get(0).eResource().getContents().get(0), (EClass)parsedConstraints.get(0).getConstrainedElements().get(0), ResourceType.NETWORKED);
		}
		else {
			throw new ConstraintCreationException("No name was provided for the constraint");
		}

		return constructedConstraint;
	}

}
